﻿using System;
namespace Velocity4Net.Runtime.Parser.Node
{

    /*
     * Licensed to the Apache Software Foundation (ASF) under one
     * or more contributor license agreements.  See the NOTICE file
     * distributed with this work for additional information
     * regarding copyright ownership.  The ASF licenses this file
     * to you under the Apache License, Version 2.0 (the
     * "License"); you may not use this file except in compliance
     * with the License.  You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing,
     * software distributed under the License is distributed on an
     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     * KIND, either express or implied.  See the License for the
     * specific language governing permissions and limitations
     * under the License.
     */












    /**
     * handles the range 'operator'  [ n .. m ]
     *
     * Please look at the Parser.jjt file which is
     * what controls the generation of this class.
     *
     * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
     */
    public class ASTIntegerRange : SimpleNode
    {
        /**
         * @param id
         */
        public ASTIntegerRange(int id) : base(id)
        {
        }

        /**
         * @param p
         * @param id
         */
        public ASTIntegerRange(Parser p, int id) : base(p, id)
        {
        }

        /**
         * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object)
         */
        public Object jjtAccept(ParserVisitor visitor, Object data)
        {
            return visitor.visit(this, data);
        }

        public static class IntegerRange : AbstractList<Integer>
        {
            public class RangeIterator,ListIterator<Integer>
        {
            private int value;

            public RangeIterator()
            {
                value = left - delta;
            }

            public RangeIterator(int startIndex)
            {
                value = left + (startIndex - 1) * delta;
            }


            public override Integer next()
            {
                value += delta;
                return value;
            }


            public override bool hasPrevious()
            {
                return value != left - delta;
            }


            public override Integer previous()
            {
                value -= delta;
                return value;
            }


            public override int nextIndex()
            {
                return (value + delta - left) * delta;
            }


            public override int previousIndex()
            {
                return (value - delta - left) * delta;
            }


            public override void remove()
            {
                throw new UnsupportedOperationException("integer range is read only");
            }


            public override void set(Integer integer)
            {
                throw new UnsupportedOperationException("integer range is read only");
            }


            public override void add(Integer integer)
            {
                throw new UnsupportedOperationException("integer range is read only");
            }


            public override bool hasNext()
            {
                return value != right;
            }
        }

        private int left;
        private int right;
        private int delta;

        public IntegerRange(int left, int right, int delta)
        {
            this.left = left;
            this.right = right;
            this.delta = delta;
        }


        public override Iterator<Integer> iterator()
        {
            return new RangeIterator();
        }


        public override Integer get(int index)
        {
            int ret = left + delta * index;
            if (delta > 0 && ret > right || delta < 0 && ret < right)
            {
                throw new IndexOutOfBoundsException();
            }
            return ret;
        }


        public override int indexOf(Object o)
        {
            int v = DuckType.asNumber(o).intValue();
            v -= left;
            v *= delta;
            return v >= 0 && v < size() ? v : -1;
        }


        public override int lastIndexOf(Object o)
        {
            return indexOf(o);
        }


        public override ListIterator<Integer> listIterator()
        {
            return new RangeIterator();
        }


        public override ListIterator<Integer> listIterator(int index)
        {
            return new RangeIterator(index);
        }


        public override int size()
        {
            return Math.abs(right - left) + 1;
        }
    }

    /**
     *  does the real work.  Creates an Vector of Integers with the
     *  right value range
     *
     *  @param context  app context used if Left or Right of .. is a ref
     *  @return Object array of Integers
     * @throws MethodInvocationException
     */
    public Object value(InternalContextAdapter context)
    {
        /*
         *  get the two range ends
         */

        Object left = jjtGetChild(0).value(context);
        Object right = jjtGetChild(1).value(context);

        /*
         *  if either is null, lets log and bail
         */

        if (left == null || right == null)
        {
            log.Error((left == null ? "Left" : "Right")
                           + " side of range operator [n..m] has null value."
                           + " Operation not possible. "
                           + StringUtils.formatFileString(this));
            return null;
        }

        /*
         *  if not a Number, try to convert
         */

        try
        {
            left = DuckType.asNumber(left);
        }
        catch (NumberFormatException nfe) { }

        try
        {
            right = DuckType.asNumber(right);
        }
        catch (NumberFormatException nfe) { }

        /*
         *  if still not a Number, nothing we can do
         */

        if (!(left is Number) || !(right is Number))
        {
            log.Error((!(left is Number) ? "Left" : "Right")
                           + " side of range operator is not convertible to a Number. "
                           + StringUtils.formatFileString(this));
            return null;
        }

        /*
         *  get the two integer values of the ends of the range
         */

        int l = ((Number)left).intValue();
        int r = ((Number)right).intValue();

        /*
         *  Determine whether the increment is positive or negative.
         */

        int delta = (l >= r) ? -1 : 1;

        /*
         * Return the corresponding integer range
         */

        return new IntegerRange(l, r, delta);
    }

    /**
     * @throws TemplateInitException
     * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)
     */
    public Object init(InternalContextAdapter context, Object data)
    {
        Object obj = super.init(context, data);
        cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens
        return obj;
    }

}
}